﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static System.Runtime.InteropServices.JavaScript.JSType;
using Zhy.ModbusLib.Base;
using System.IO.Ports;

namespace Zhy.ModbusLib
{
    public class ModbusRTU : ModbusSerial
    {
        #region 初始化
        /// <summary>
        /// 1、构造函数的初始化
        /// 2、有实例了，开始与设备进行连接
        /// </summary>
        public ModbusRTU() :
            this("COM1")
        { }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="portName">端口名称</param>
        public ModbusRTU(string portName) : this(portName, 9600, Parity.None, 8, StopBits.One)
        { }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="portName">端口名称</param>
        /// <param name="baudRate">波特率</param>
        /// <param name="parity">校验位</param>
        /// <param name="dataBits">数据位</param>
        /// <param name="stopBits">停止位</param>
        public ModbusRTU(string portName, int baudRate, Parity parity, int dataBits, StopBits stopBits) : base()
        {
            this.PortName = portName; // 端口号
            this.BaudRate = baudRate; // 波特率
            this.Parity = parity; // 校验位
            this.DataBits = dataBits; // 数据位
            this.StopBits = stopBits; // 停止位
        }
        #endregion

        #region Modbus读取数据
        /// <summary>
        /// 读取 4个存储区域
        //  从站地址、功能码、起始地址、数量
        /// </summary>
        /// <param name="slave">从站地址</param>
        /// <param name="func">功能码</param>
        /// <param name="start">起始位置</param>
        /// <param name="count">读取个数</param>
        /// <returns></returns>
        public override byte[] Read(byte slave, Functions func, ushort start, ushort count)
        {
            // 要发送的报文
            List<byte> bytes =
               this.GetReadBytes(slave, (byte)func, start, count).ToList();

            bytes = CreateAdu(bytes); // 添加校验码

            // 适用于03，04功能码
            // 如果请求10个寄存器，正常响应25个字节，
            // 正常报文接收长度 适用于03，04功能码
            int len = count * 2 + 5;

            // 正常报文接收长度 适用01、02 - 如果功能码是01 02 重新计算报文接收长度
            // 如果请求10个线圈，正常响应7
            if (func == Functions.RCoilStatus || func == Functions.RInputCoils)
                len = (int)Math.Ceiling(count * 1.0 / 8) + 5; // 8个线圈是一个字节

            // 发送和接收报文数据 这个逻辑   适用  RTU   ASCII
            byte[] resp = this.SendAndReceive(bytes.ToArray(), len);
            // 返回的报文进行校验
            return this.Check(resp, len); // 返回接收的报文
        }

        /// <summary>
        /// 读取 4个存储区域
        //  从站地址、功能码、起始地址、数量
        /// </summary>
        /// <param name="slave">从站地址</param>
        /// <param name="func">功能码</param>
        /// <param name="start">起始位置</param>
        /// <param name="count">读取个数</param>
        /// <param name="callback">委托用于处理返回的数据</param>
        /// <returns></returns>
        public override void ReadAsync(
            byte slave, Functions func, ushort start, ushort count,
            int handler, Action<byte[], int, Exception> callback)
        {
            // 要发送的报文
            List<byte> bytes =
                this.GetReadBytes(slave, (byte)func, start, count).ToList();

            bytes = CreateAdu(bytes); // 添加校验码
            // 适用于03，04功能码
            // 如果请求10个寄存器，正常响应25个字节，
            // 正常报文接收长度 适用于03，04功能码
            int len = count * 2 + 5;
            // 正常报文接收长度 适用01、02 - 如果功能码是01 02 重新计算报文接收长度
            // 如果请求10个线圈，正常响应7
            if (func == Functions.RCoilStatus || func == Functions.RInputCoils)
                len = (int)Math.Ceiling(count * 1.0 / 8) + 5; // 8个线圈是一个字节

            // 向队列里添加委托任务-用于处理异步返回的数据
            this.asyncModels.Add(new AsyncModel
            {
                Handler = handler, // 请求ID
                ReqBytes = bytes.ToArray(), // 请求数据
                RespLen = len, // 返回数据长度
                Completed = callback // 回调委托
            });
            // 会有一个线程从队列里取任务执行
        }
        #endregion

        #region Modbus RTU 写入操作
        /// <summary>
        /// Modbus RTU 写入操作
        /// </summary>
        /// <param name="slave">从站地址</param>
        /// <param name="func">功能码</param>
        /// <param name="start">起始地址</param>
        /// <param name="count">字节数</param>
        /// <param name="datas">写入数据</param>
        public override void Write(byte slave, Functions func, ushort start, ushort count, byte[] datas)
        {
            // 拼接要写入的报文
            List<byte> bytes =
                this.GetWriteBytes(slave, (byte)func, start, count, datas).ToList();
            // 添加校验码
            bytes = this.CreateAdu(bytes);
            // 发送和接收返回报文方法
            // 固定8个字节(1个地址+1个功能码+2个写入地址+2个写入数量+2个校验位)
            byte[] resp = this.SendAndReceive(bytes.ToArray(), 8);
            // 返回的报文进行校验
            this.Check(resp, 8);
            // 如果写入失败会发生异常
        }

        /// <summary>
        /// Modbus RTU 写入操作
        /// </summary>
        /// <param name="slave">从站地址</param>
        /// <param name="func">功能码</param>
        /// <param name="start">起始地址</param>
        /// <param name="count">字节数</param>
        /// <param name="datas">写入数据</param>
        /// <param name="callback">委托用于处理返回的数据</param>
        public override void WriteAsync(
            byte slave, Functions func, ushort start, ushort count, byte[] datas,
            int handler, Action<byte[], int, Exception> callback)
        {
            // 拼接要写入的报文
            List<byte> bytes =
                this.GetWriteBytes(slave, (byte)func, start, count, datas).ToList();

            bytes = this.CreateAdu(bytes); // 添加校验码

            this.asyncModels.Insert(0, new AsyncModel
            {
                Handler = handler,
                ReqBytes = bytes.ToArray(),
                RespLen = 8,
                Completed = callback
            });
        }
        #endregion

        /// <summary>
        /// 添加校验码
        /// </summary>
        /// <param name="bytes">要发送的报文</param>
        /// <returns></returns>
        private List<byte> CreateAdu(List<byte> bytes)
        {
            byte[] crc = CRC16(bytes); // 生成校验码
            bytes.AddRange(crc.Reverse());  // 添加校验位，是否反转取决于设备的大小端
            return bytes; // 返回要发送的报文
        }

        /// <summary>
        /// 返回的报文进行校验
        /// </summary>
        /// <param name="resp">返回的报文</param>
        /// <param name="len">正常报文接收长度</param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        protected override byte[] Check(byte[] resp, int len)
        {
            // resp.Length == 5 表示异常码的报文（1个地址+1个功能码+1个故障原因+2个校验码）。
            // resp.Length == len 表示正常返回的报文
            if (resp.Length == 5 || resp.Length == len)
            {
                // 获取除去验证码的报文
                List<byte> check = resp.ToList().GetRange(0, resp.Length - 2);
                // 添加重新生成的校验码 - 反转是大小端的原因
                check.AddRange(CRC16(check).Reverse());
                // 判断接收的报文格式是否正确
                if (!check.SequenceEqual(resp))
                {
                    throw new Exception("数据传输异常，校验码不匹配");
                }

                // 报文异常码检查
                // 0x03          0x83
                // 0000 0011     1000 0011
                if (resp[1] > 0x80) // 功能码>0x80的是异常报文-否则是正常报文
                {
                    // resp[2] 是异常码 - 获取异常码的故障原因
                    throw new Exception(ModbusBase.Errors[resp[2]]);// 异常码
                }
                // 截取实际的报文数据-数据字节
                return resp.ToList().GetRange(3, len - 5).ToArray();
            }
            else
            {
                // 报文无法识别 - 通信报文响应异常
                throw new Exception("通信响应异常");
            }
        }
    }
}
